#ifndef XG_REDISCONNECT_H
#define XG_REDISCONNECT_H
////////////////////////////////////////////////////////
#include "DBConnect.h"

#ifndef XG_REDIS_BUFFER_MAXSIZE
#define XG_REDIS_BUFFER_MAXSIZE		1024 * 1024
#endif

class RedisConnect : public Object
{
	friend class Command;

public:
	class Command
	{
		friend RedisConnect;

	protected:
		int status;
		string msg;
		vector<string> res;
		vector<string> vec;

	protected:
		int parse(const char* msg, int len);
		const char* parseNode(const char* msg, int len);

	public:
		string toString() const;
		int getResult(RedisConnect* redis, int timeout);

	public:
		Command()
		{
			this->status = 0;
		}
		Command(const string& cmd)
		{
			vec.push_back(cmd);
			this->status = 0;
		}

		string get(int idx) const
		{
			return res.at(idx);
		}
		void add(const char* val)
		{
			vec.push_back(val);
		}
		void add(const string& val)
		{
			vec.push_back(val);
		}
		const vector<string>& getDataList() const
		{
			return res;
		}
		template<class DATA_TYPE> void add(DATA_TYPE val)
		{
			add(stdx::str(val));
		}
		template<class DATA_TYPE, class ...ARGS> void add(DATA_TYPE val, ARGS ...args)
		{
			add(val);
			add(args...);
		}
	};

protected:
	int code;
	int port;
	int memsz;
	int status;
	int timeout;

	string msg;
	string host;
	Socket sock;
	string passwd;
	SmartBuffer buffer;

public:
	int getPort() const
	{
		return port;
	}
	int getStatus() const
	{
		return status;
	}
	int getErrorCode() const
	{
		if (sock.isClosed()) return XG_ERROR;

		return code < 0 ? code : 0;
	}
	SmartBuffer getBuffer() const
	{
		return buffer;
	}
	const string& getHost() const
	{
		return host;
	}
	const string& getPassword() const
	{
		return passwd;
	}
	const string& getErrorString() const
	{
		return msg;
	}

public:
	RedisConnect()
	{
		this->code = 0;
		this->port = 0;
		this->memsz = 0;
		this->status = 0;
		this->timeout = 0;
	}
	bool reconnect()
	{
		if (host.empty()) return false;

		return connect(host.c_str(), port, timeout, memsz) && auth(passwd) > 0;
	}
	int execute(Command& cmd)
	{
		return cmd.getResult(this, timeout);
	}
	template<class DATA_TYPE, class ...ARGS>
	int execute(DATA_TYPE val, ARGS ...args)
	{
		Command cmd;

		cmd.add(val, args...);

		return cmd.getResult(this, timeout);
	}
	template<class DATA_TYPE, class ...ARGS>
	int execute(vector<string>& vec, DATA_TYPE val, ARGS ...args)
	{
		Command cmd;

		cmd.add(val, args...);

		cmd.getResult(this, timeout);

		if (code > 0) std::swap(vec, cmd.res);

		return code;
	}
	bool connect(const string& host, int port, int timeout = SOCKET_CONNECT_TIMEOUT, int memsz = XG_REDIS_BUFFER_MAXSIZE)
	{
		CHECK_FALSE_RETURN(sock.connect(host, port, timeout));

		this->host = host;
		this->port = port;
		this->memsz = memsz;
		this->timeout = timeout;

		return buffer.malloc(memsz) ? true : false;
	}

public:
	int ping()
	{
		return execute("ping");
	}
	int del(const string& key)
	{
		return execute("del", key);
	}
	int ttl(const string& key)
	{
		return execute("ttl", key) == XG_OK ? status : code;
	}
	int hlen(const string& key)
	{
		return execute("hlen", key) == XG_OK ? status : code;
	}
	int auth(const string& passwd)
	{
		this->passwd = passwd;

		if (passwd.empty()) return XG_OK;

		return execute("auth", passwd);
	}
	int get(const string& key, string& val)
	{
		vector<string> vec;

		if (execute(vec, "get", key) <= 0) return code;

		val = vec[0];

		return code;
	}
	int decr(const string& key, int val = 1)
	{
		return execute("decrby", key, val);
	}
	int incr(const string& key, int val = 1)
	{
		return execute("incrby", key, val);
	}
	int expire(const string& key, int timeout)
	{
		return execute("expire", key, timeout);
	}
	int keys(vector<string>& vec, const string& key)
	{
		return execute(vec, "keys", key);
	}
	int hdel(const string& key, const string& filed)
	{
		return execute("hdel", key, filed);
	}
	int hget(const string& key, const string& filed, string& val)
	{
		vector<string> vec;

		if (execute(vec, "hget", key, filed) <= 0) return code;

		val = vec[0];

		return code;
	}
	int set(const string& key, const string& val, int timeout = 0)
	{
		return timeout > 0 ? execute("setex", key, timeout, val) : execute("set", key, val);
	}
	int hset(const string& key, const string& filed, const string& val)
	{
		return execute("hset", key, filed, val);
	}

	string get(const string& key)
	{
		string res;

		get(key, res);

		return res;
	}
	string hget(const string& key, const string& filed)
	{
		string res;

		hget(key, filed, res);

		return res;
	}

public:
	int pop(const string& key, string& val)
	{
		return lpop(key, val);
	}
	int lpop(const string& key, string& val)
	{
		vector<string> vec;

		if (execute(vec, "lpop", key) <= 0) return code;

		val = vec[0];

		return code;
	}
	int rpop(const string& key, string& val)
	{
		vector<string> vec;

		if (execute(vec, "rpop", key) <= 0) return code;

		val = vec[0];

		return code;
	}
	int push(const string& key, const string& val)
	{
		return rpush(key, val);
	}
	int lpush(const string& key, const string& val)
	{
		return execute("lpush", key, val);
	}
	int rpush(const string& key, const string& val)
	{
		return execute("rpush", key, val);
	}
	int range(vector<string>& vec, const string& key, int start, int end)
	{
		return execute(vec, "lrange", key, start, end);
	}
	int lrange(vector<string>& vec, const string& key, int start, int end)
	{
		return execute(vec, "lrange", key, start, end);
	}

public:
	int zrem(const string& key, const string& filed)
	{
		return execute("zrem", key, filed);
	}
	int zadd(const string& key, const string& filed, int score)
	{
		return execute("zadd", key, score, filed);
	}
	int zrange(vector<string>& vec, const string& key, int start, int end, bool withscore = false)
	{
		return withscore ? execute(vec, "zrange", key, start, end, "withscores") : execute(vec, "zrange", key, start, end);
	}

public:
	template<class ...ARGS>
	int eval(const string& lua)
	{
		vector<string> vec;

		return eval(lua, vec);
	}
	template<class ...ARGS>
	int eval(const string& lua, const string& key, ARGS ...args)
	{
		vector<string> vec;
	
		vec.push_back(key);
	
		return eval(lua, vec, args...);
	}
	template<class ...ARGS>
	int eval(const string& lua, const vector<string>& keys, ARGS ...args)
	{
		vector<string> vec;

		return eval(vec, lua, keys, args...);
	}
	template<class ...ARGS>
	int eval(vector<string>& vec, const string& lua, const vector<string>& keys, ARGS ...args)
	{
		int len = 0;
		Command cmd("eval");

		cmd.add(lua);
		cmd.add(len = keys.size());

		if (len-- > 0)
		{
			for (int i = 0; i < len; i++) cmd.add(keys[i]);

			cmd.add(keys.back(), args...);
		}

		cmd.getResult(this, timeout);
	
		if (code > 0) std::swap(vec, cmd.res);

		return code;
	}

	string getLockId()
	{
		thread_local string id;

		if (id.empty())
		{
			int num = 0;
			char* host[32];

			if (GetLocalAddress(host) > 0) num = GetHostInteger(*host);

			stdx::append(id, "%d:%ld:%ld", num, (u_long)Process::GetCurrentProcess(), (u_long)GetCurrentThreadId());
		}

		return id;
	}
	bool unlock(const string& key)
	{
		const char* lua = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

		return eval(lua, key, getLockId()) > 0 && status == XG_OK;
	}
	bool lock(const string& key, int timeout = 30)
	{
		int delay = timeout * 1000;

		for (int i = 0; i < delay; i += 10)
		{
			if (execute("set", key, getLockId(), "nx", "ex", timeout) >= 0) return true;

			Sleep(10);
		}

		return false;
	}

public:
	static bool CanUse();
	static sp<RedisConnect> Instance();
	static void Setup(const string& host, int port, const string& passwd = "", int timeout = SOCKET_CONNECT_TIMEOUT, int memsz = XG_REDIS_BUFFER_MAXSIZE);

protected:
	static RedisConnect* GetTemplate();
	virtual sp<RedisConnect> grasp() const;
};

class RedisSession : public Session
{
private:
	long ctime;
	sp<RedisConnect> redis;

public:
	bool remove(const string& key)
	{
		return redis->del(key) > 0;
	}
	string get(const string& key) const
	{
		string val;

		get(key, val);

		return val;
	}
	bool set(const string& key, int val)
	{
		return set(key, stdx::str(val));
	}
	bool get(const string& key, int& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atoi(tmp.c_str());

		return true;
	}
	bool get(const string& key, long& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atol(tmp.c_str());

		return true;
	}
	bool get(const string& key, double& val) const
	{
		string tmp;

		CHECK_FALSE_RETURN(get(key, tmp));

		val = stdx::atof(tmp.c_str());

		return true;
	}
	bool get(const string& key, string& val) const
	{
		return redis->hget(name, key, val) >= 0;
	}
	bool set(const string& key, const string& val)
	{
		return redis->hset(name, key, val) >= 0;
	}

public:
	bool clear()
	{
		CHECK_FALSE_RETURN(ctime > 0 && disable());

		return set("{ctime}", stdx::str(ctime));
	}
	bool disable()
	{
		return redis->del(name);
	}
	int size() const
	{
		return redis->hlen(name);
	}
	bool empty() const
	{
		return size() == 0;
	}
	long getTimeout() const
	{
		return redis->ttl(name);
	}
	bool isTimeout() const
	{
		return getTimeout() <= 0;
	}
	long getCreateTime() const
	{
		return ctime;
	}
	long setTimeout(long second)
	{
		return redis->expire(name, second);
	}

public:
	void close()
	{
		redis = NULL;
		ctime = 0;
	}
	int getErrorCode() const
	{
		return redis ? redis->getErrorCode() : XG_NETERR;
	}
	string getErrorString() const
	{
		return redis ? redis->getErrorString() : "connect error";
	}
	bool init(const string& name, int timeout)
	{
		if (redis) disable();

		CHECK_FALSE_RETURN(redis = RedisConnect::Instance());

		this->name = name;

		if (timeout <= 0) return get("{ctime}", ctime) && ctime > 0;

		ctime = time(NULL);

		CHECK_FALSE_RETURN(set("{ctime}", stdx::str(ctime)));

		return redis->expire(name, timeout) > 0;
	}
};

////////////////////////////////////////////////////////
#endif